#include "src/scene/bumpMapping.h"

#include "src/scene/object.h"

#include <GL/glu.h>
#include <iostream>
#include <cmath>

#define DELTA 0.00001
//#define DELTA 0.1

BumpMapping::BumpMapping(const QString &_imagePathName, unsigned int _repeatTimesU, unsigned int _repeatTimesV, unsigned int _mappingType, double _bumpIntensity, bool _loop)
    : bumpMappingImagePathName(_imagePathName), mappingType(_mappingType), repeatTimesU(_repeatTimesU), repeatTimesV(_repeatTimesV), bumpIntensity(_bumpIntensity), loop(_loop)
{
    bumpMappingImage.load(_imagePathName);
}

Vector3D BumpMapping::getNormal(Object *_object, const Vector3D &_point, const Vector3D &_normal) const
{


//    std::cout <<"\n\nTentando conseguir a normal afetada pelo bumpmapping de :"; _normal.print();



    double x, y;
    _object->getTextureUV(&x, &y, _point, mappingType);

    int w = bumpMappingImage.width();
    int h = bumpMappingImage.height();

    int u = (int)(x * w * repeatTimesU) % w;
    int v = (int)(y * h * repeatTimesV) % h;


//    //Normalized distance between texels.
//    double tex_dist_u = 1.0/w;
//    double tex_dist_v = 1.0/h;

    //Aproximation of the gradient for a given point ui by computing the
    //central difference (i.e., u'i = ui+h-ui-h) in each UV coordinate.
    Vector3D du, dv;
    //Checking the boundary conditions. The gradient on the borders
    //is the same point.

    if(loop)
    {
        if(u == 0)
            du = colorToVector(bumpMappingImage.pixel(u, v)) -
                 colorToVector(bumpMappingImage.pixel(w - 1, v));
        else if(u == w - 1)
            du = colorToVector(bumpMappingImage.pixel(0, v)) -
                 colorToVector(bumpMappingImage.pixel(u, v));
        //If we are at any central point, then compute the gradient as usual.
        else
            du = colorToVector(bumpMappingImage.pixel(u - 1, v)) -
                 colorToVector(bumpMappingImage.pixel(u + 1, v));

        //the same for the other direction
        //Checking the boundary conditions.
        if(v == 0)
            dv = colorToVector(bumpMappingImage.pixel(u, h - 1)) -
                 colorToVector(bumpMappingImage.pixel(u, v));
        else if(v == h - 1)
            dv = colorToVector(bumpMappingImage.pixel(u, v)) -
                 colorToVector(bumpMappingImage.pixel(u, 0));
        //If we are at any central point, then compute the gradient as usual.
        else
            dv = colorToVector(bumpMappingImage.pixel(u, v + 1)) -
                 colorToVector(bumpMappingImage.pixel(u, v - 1));
    }
    else
    {
        if(u == 0)
            du = colorToVector(bumpMappingImage.pixel(u, v)) -
                 colorToVector(bumpMappingImage.pixel(u + 1, v));
        else if(u == w - 1)
            du = colorToVector(bumpMappingImage.pixel(u - 1, v)) -
                 colorToVector(bumpMappingImage.pixel(u, v));
        //If we are at any central point, then compute the gradient as usual.
        else
            du = colorToVector(bumpMappingImage.pixel(u - 1, v)) -
                 colorToVector(bumpMappingImage.pixel(u + 1, v));

        //the same for the other direction
        //Checking the boundary conditions.
        if(v == 0)
            dv = colorToVector(bumpMappingImage.pixel(u, v + 1)) -
                 colorToVector(bumpMappingImage.pixel(u, v));
        else if(v == h - 1)
            dv = colorToVector(bumpMappingImage.pixel(u, v)) -
                 colorToVector(bumpMappingImage.pixel(u, v - 1));
        //If we are at any central point, then compute the gradient as usual.
        else
            dv = colorToVector(bumpMappingImage.pixel(u, v + 1)) -
                 colorToVector(bumpMappingImage.pixel(u, v - 1));
    }


    //Gray color of the gradient is normalized, i.e., average gray value.
    double u_val = (du.x + du.y + du.z)/3.0;
    double v_val = (dv.x + dv.y + dv.z)/3.0;


    //The bump intensity, as well as the average of the difference
    //(which was missing above).
    u_val *= 0.5*bumpIntensity;
    v_val *= 0.5*bumpIntensity;


    //calculating the local coordinates
    Vector3D v1(1.0, 0, 0);
	if( fabs(fabs(_normal.dotProduct(v1)) - 1) < ERROR)
          v1 = Vector3D(0, 1.0, 0);
	//Local orthonormal basis.
	Vector3D tangent = _normal.crossProduct(v1).normalize();
    Vector3D biNorm = _normal.crossProduct(tangent).normalize();

    Vector3D result = _normal + (tangent*u_val) + (biNorm*v_val);


//    std::cout <<"\t resultado:"; result.normalize().print();

    return result.normalize();
}

Vector3D colorToVector(const QRgb &_color)
{
    return Vector3D(qRed(_color), qGreen(_color), qBlue(_color));
}
